package dkbta.ontology.elements.stat;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import dkbta.ontology.Ontology;
import dkbta.ontology.elements.Element;
import dkbta.ontology.elements.ElementDef;
import dkbta.ontology.elements.ElementType;
import dkbta.ontology.loading.OntologyException;
import dkbta.ontology.values.PercentValues;
import dkbta.ontology.values.ValueCondition;
import dkbta.processing.ActionTimer;
import dkbta.processing.Patient;
import dkbta.processing.Patients;
import dkbta.util.InstanceTime;
import dkbta.util.Pair;
import dkbta.util.time.TimeInterval;

public class StatDef extends ElementDef{
	/** The condition on which the statistical abstraction is based */
	private final ValueCondition _condition;

	private final PercentValues _percentRange;

	private final boolean _ignorePatientsWithoutElement;

	/**
	 * Initializes a statistical abstraction definition
	 * 
	 * @param name The element's name
	 * @param componentType The element's component type
	 */
	public StatDef(String name, String componentType, ValueCondition valueCondition,
		PercentValues percentRange, boolean ignorePatientsWithoutElement){
		super(ElementType.STAT, name, componentType, null);
		_condition = valueCondition;
		_percentRange = percentRange;
		_ignorePatientsWithoutElement = ignorePatientsWithoutElement;

	}

	@Override
	public void validate(Ontology ontology) throws OntologyException{
		_condition.validate(ontology);
		String elementName = _condition.getElementName();
		ElementType elementType = _condition.getElementType();
		_prerequisiteElements.add(ontology.getElement(elementType, elementName));
	}

	@Override
	public String completeToString(){
		StringBuilder sb = new StringBuilder(super.completeToString());
		sb.append("Condition: ").append(_condition).append("\r\n");
		sb.append("Percent Range: ").append(_percentRange).append("\r\n");
		sb.append("Ignore Patients Without Element: ").append(
			_ignorePatientsWithoutElement).append("\r\n");
		return sb.toString();
	}

	@Override
	protected void compute(Patient p){
		throw new UnsupportedOperationException(
				"Statistical abstractions aren't supposed to be computed this way! Use compue(Paients, Pair");
	}

	@Override
	protected void compute(Patients patients, Pair<Integer, Integer>[] patientIds){
		ActionTimer actionTimer = ActionTimer.getInstance();
		int statActionId = actionTimer.actionStart();

		String elementName = _condition.getElementName();
		ElementType elementType = _condition.getElementType();
		List<Stat> stats = patients.getStats(_name);

		List<InstanceTime> times = new ArrayList<InstanceTime>();
		int numOfRequiredElements = 0;
		Iterator<Patient> iter = patients.rangeIterator(patientIds);
		while (iter.hasNext()){
			Patient p = iter.next();
			List<? extends Element> elements = p.getElements(elementType, elementName);
			if (elements.isEmpty()){
				if (_ignorePatientsWithoutElement){
					continue;
				}
			}

			// For each instance,f it meets the condition, adding its start and end times
			// to the times list
			for (Element e : elements){
				if (_condition.checkCondition(e)){
					TimeInterval ti = e.getTimeInterval();
					times.add(new InstanceTime(e, numOfRequiredElements, ti
							.getStartTime(), true));
					times.add(new InstanceTime(e, numOfRequiredElements, ti.getEndTime(),
							false));
				}
			}
			++numOfRequiredElements;
		}

		// Sorting the time list chronologically
		Collections.sort(times);

		// Going over the times to find all of the overlaps
		TimeInterval lastTI = null;
		int currentlyOverlapping = 0;
		Element[] currentElements = new Element[numOfRequiredElements];
		for (InstanceTime it : times){
			if (it.isStart){
				currentElements[it.elementIndex] = it.element;
				++currentlyOverlapping;
			}else{
				currentElements[it.elementIndex] = null;
				--currentlyOverlapping;
			}

			double overlapPercent = (currentlyOverlapping / (double)numOfRequiredElements) * 100.0;

			// Checking if the percent of overlapping elements is in the valid range
			if (_percentRange.isValid(overlapPercent)){
				// Obtaining the overlap interval
				TimeInterval currentTI = new TimeInterval(0, Long.MAX_VALUE);
				for (Element e : currentElements){
					if (e != null){
						currentTI = currentTI.getOverlap(e.getTimeInterval());
						if (currentTI == null){
							throw new IllegalArgumentException(
									"Element instances don't overlap!");
						}
					}
				}

				// Checking if we have a completely new interval
				// or a continuation of a previous one
				TimeInterval overlap = currentTI.getOverlap(lastTI);
				if (overlap == null){
					stats.add(new Stat(_name, currentTI));
					lastTI = currentTI;
				}else{
					lastTI.uniteWith(currentTI);
				}
			}
		}

		actionTimer.actionEnd(statActionId, -1, _name, _type,
			ActionTimer.Action.STATISTICAL_ABSTRACTION_COMPUTATION);
	}

	@Override
	public Stat newInstance(long startTime, long endTime, String[] value){
		throw new UnsupportedOperationException(
				"Statistical abstractions aren't supposed to be instantiated this way!");
	}

}
